#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netdb.h>
#include <iostream>
#include <fstream>
#include <limits.h>

#define MYPORT "4950"    // the port users will be connecting to
#define MAXBUFLEN 65507
#define HEADER_SIZE 6
#define ACK_HEADER_SIZE 6

typedef unsigned char uchar;
typedef unsigned int uint;
typedef unsigned short ushort;

using namespace std;

class Receiver{
	protected:
	    int sockfd;
	    struct addrinfo hints, *servinfo, *p;
	    int rv;
	    int numbytes;
	    struct sockaddr_storage their_addr;
	    uchar buf[MAXBUFLEN];	//Buffer for receiving packet. 4 bytes for sequence number, 2 bytes for checksum, rest for data.
	    socklen_t addr_len;
		fstream fout;
	    //uchar s[INET6_ADDRSTRLEN];
		uchar ack[5];	//Acknowledgement packet. 4 bytes for sequence number, 1 byte for ack or negative ack. 
	private:
	
	
	public:
		Receiver(){
			bindSocket();
			while(1){
				memset(buf,0,0);
				memset(ack,0,0);
				addr_len = sizeof their_addr;
		   		if ((numbytes = recvfrom(sockfd, buf, MAXBUFLEN, 0,
		        	(struct sockaddr *)&their_addr, &addr_len)) == -1){
		        		perror("recvfrom");
		        		exit(1);
				} else {
					cout << "Packet " << getSeqNum(buf) << " received (" << numbytes << " bytes)" << endl;
					//cout << "Checksum: " << getChecksum(buf) << endl;
				}
				//printdata(buf,numbytes);
				//do checksum
				if(cksum(buf,numbytes)){
					//if checksum succeeded, write bytes to file and send ack
					if(getSeqNum(buf) == 0){	//We have a new sequence of packets. New file.
						int fileNameLength = numbytes-HEADER_SIZE;
						char fileName[fileNameLength];
						for(int j = (int)HEADER_SIZE; j < numbytes; j++){
							fileName[j-HEADER_SIZE] = buf[j];
						}
						fileName[fileNameLength] = '\0';
						string tempFileName(fileName);
						tempFileName = "/tmp/ismajors/" + tempFileName;
						cout << "Creating new file: " << tempFileName << endl;
						fout.open(tempFileName.c_str(), ios::out | ios::binary);
					} else if(getSeqNum(buf) == UINT_MAX){
						cout << "Received eof." << endl;
						fout.close();
					} else {
						for(int j = (int)HEADER_SIZE; j < numbytes; j++){
							if(fout.good()) {
									fout.put(buf[j]);
							}
						}
					}
					sendAck(getSeqNum(buf));
				}
			}
		}
		
		~Receiver(){
			close(sockfd);
		}
		
		ushort cksum(uchar* addr, int count) {
			/* Compute Internet Checksum for "count" bytes
			*         beginning at location "addr".
			*/
			register long sum = 0;
			ushort* shrt_addr = (ushort*)addr;
			while( count > 1 )  {
				/*  This is the inner loop */
			   sum += *shrt_addr++;
			   count -= 2;
			}
			
			/*  Add left-over byte, if any */
			if( count > 0 ){
				sum += *addr;
			}

			/*  Fold 32-bit sum to 16 bits */
			while (sum>>16)
				sum = (sum & 0xffff) + (sum >> 16);
			if(sum == 0xFFFF){
				cout << "Checksum succeeded." << endl;
				return true;
			} else {
				printf("Checksum failed: %X\n",sum);
				return false;
			}
		}
		
		void printdata(uchar* data, int count){
			cout << " | Data: ";
			for(int i = 0; i < count;i++){
				printf("[%d]: 0x%X ",i,data[i]);
			}
			cout << " |" << endl;
		}
		
		ushort getChecksum(uchar* data){
			ushort cksum = (data[5] << 8) + data[4];
			return cksum;
		}
		
		void clearChecksum(uchar* addr){
			addr[4] = 0;
			addr[5] = 0;
		}
		
		void sendAck(ushort sequenceNumber){
			setSeqNum(ack, sequenceNumber);
			ack[5] = 0x06;	//ACK ascii code.
			if ((numbytes = sendto(sockfd, ack, 5, 0, (struct sockaddr *)&their_addr, addr_len)) == -1){
				perror("Receiver: sendto");
				exit(1);
			} else {
				cout << "Sent ack." << endl;
			}
		}
		
		uint getSeqNum(uchar* data){
			uint seq = (data[3] << 24) + (data[2] << 16) + (data[1] << 8) + data[0];
			return seq;
		}
		
		void setSeqNum(uchar* packet, uint sequenceNumber){
			packet[0] = (sequenceNumber & 0x000000FF);
			packet[1] = ((sequenceNumber & 0x0000FF00) >> 8);
			packet[2] = ((sequenceNumber & 0x00FF0000) >> 16);
			packet[3] = ((sequenceNumber & 0xFF000000) >> 24);
		}
		
		// get sockaddr, IPv4 or IPv6:
		void *get_in_addr(struct sockaddr *sa) {
		    if (sa->sa_family == AF_INET) {
		        return &(((struct sockaddr_in*)sa)->sin_addr);
		    }
		    return &(((struct sockaddr_in6*)sa)->sin6_addr);
		}
		
		int bindSocket(){
			cout << "Binding socket..." << endl;
			memset(&hints, 0, sizeof hints);
		    hints.ai_family = AF_UNSPEC; // set to AF_INET to force IPv4
		    hints.ai_socktype = SOCK_DGRAM;
		    hints.ai_flags = AI_PASSIVE; // use my IP
			
		    if ((rv = getaddrinfo(NULL, MYPORT, &hints, &servinfo)) != 0) {
		        fprintf(stderr, "getaddrinfo: %s\n", gai_strerror(rv));
		        return 1;
		    }
			
		    // loop through all the results and bind to the first we can
		    for(p = servinfo; p != NULL; p = p->ai_next) {
		        if ((sockfd = socket(p->ai_family, p->ai_socktype,
		                p->ai_protocol)) == -1) {
		            perror("listener: socket");
		            continue;
		        }
				
		        if (bind(sockfd, p->ai_addr, p->ai_addrlen) == -1) {
		            close(sockfd);
		            perror("listener: bind");
		            continue;
		        }
				
		        break;
		    }
			
		    if (p == NULL) {
		        fprintf(stderr, "listener: failed to bind socket\n");
		        return 2;
		    }
			
			freeaddrinfo(servinfo);
			cout << "Socket bound." << endl;
			return 0;
		}
};

int main(void) {
	Receiver();
	return 0;
}
